package common.utils.net;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;

import android.os.Environment;

public class HttpsClientUtils {

	private static final String HTTP_SCHEME = "http"; //$NON-NLS-1$
	private static final String HTTPS_SCHEME = "https"; //$NON-NLS-1$
	private static final String KEYSTORE_SUFFIX = ".jks"; //$NON-NLS-1$
	private static final String DEFAULT_KS_DIR = new File(Environment.getDataDirectory(),
			"/data/zekezang.org/datacache").getPath(); //$NON-NLS-1$
	private static final String SSL_ALGORITHM = "TLS"; //$NON-NLS-1$
	private static final String SUN_X509_ALGORITHM = "X509"; //$NON-NLS-1$
	private static final String KEY_STORE_TYPE = "BKS"; //$NON-NLS-1$
	private static final String KEYSTORE_PASSWORD = "ZEKE@AKAZAM"; //$NON-NLS-1$

	private static Map<String, KeyStore> keyStoreManager = new HashMap<String, KeyStore>();

	private static DefaultHttpClient httpClient = null;
	private HttpHost targetHost = null;
	private URL url = null;

	public HttpsClientUtils() {}

	public DefaultHttpClient getHttpsClient(HttpHost targetHost, String username, String userpass) {
		if (httpClient != null) {
			return httpClient;
		} else {
			try {
				this.url = new URL(targetHost.getSchemeName(), targetHost.getHostName(), targetHost.getPort(), "");
			} catch (MalformedURLException e) {
				e.printStackTrace();
			}
			this.targetHost = new HttpHost(url.getHost(), url.getPort(), url.getProtocol());
			httpClient = initHttpClient(url);
			this.authenticate(username, userpass);
			return getHttpClient();
		}
	}

	public void closeHttpClient() {
		if (httpClient != null) {
			httpClient.getConnectionManager().shutdown();
			httpClient = null;
		}
	}

	private DefaultHttpClient initHttpClient(URL url) {
		DefaultHttpClient httpClient = null;
		try {
			SchemeRegistry schemeRegistry = new SchemeRegistry();
			Scheme scheme = null;
			if (url.getProtocol().equalsIgnoreCase(HTTP_SCHEME)) {
				scheme = new Scheme(targetHost.getSchemeName(), PlainSocketFactory.getSocketFactory(),
						targetHost.getPort());
			} else if (url.getProtocol().equalsIgnoreCase(HTTPS_SCHEME)) {
				KeyStore keyStore = getKeyStore(url);
				SSLContext sslContext = initSSLContext(keyStore);
				SSLSocketFactory socketFactory = getSSLSocketFactory(sslContext);
				scheme = new Scheme(HTTPS_SCHEME, socketFactory, targetHost.getPort());
			}
			schemeRegistry.register(scheme);
			HttpParams params = new BasicHttpParams();
			ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
			httpClient = new DefaultHttpClient(cm, params);
		} catch (Exception e) {
			// log.error("虚机服务器不可连接");
		}

		return httpClient;
	}

	private KeyStore getKeyStore(URL url) {
		KeyStore keyStore = keyStoreManager.get(url.getHost()+ "." + url.getPort());
		if (keyStore == null) {
			try {
				File keyStoreFile = initKeyStoreFile(url);
				keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
				FileInputStream certificateStream = new FileInputStream(keyStoreFile);
				keyStore.load(certificateStream, KEYSTORE_PASSWORD.toCharArray());
				keyStoreManager.put(url.getHost()+ "." + url.getPort(), keyStore);
			} catch (Exception e) {
				// log.error(e.getMessage());
			}
		}

		return keyStore;
	}

	private SSLContext initSSLContext(KeyStore keyStore) {
		SSLContext sslContext = null;
		KeyManagerFactory keyManagerFactory = null;
		TrustManagerFactory trustManagerFactory = null;
		String algorithm = getProviderAlgorithm();
		if (algorithm == null) {
			throw new RuntimeException("Cannot find cryptographic service."); //$NON-NLS-1$
		}
		try {
			keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
			trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
			keyManagerFactory.init(keyStore, KEYSTORE_PASSWORD.toCharArray());
			trustManagerFactory.init(keyStore);

			KeyManager[] keymanagers = keyManagerFactory.getKeyManagers();
			TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();

			sslContext = SSLContext.getInstance(SSL_ALGORITHM);
			sslContext.init(keymanagers, trustManagers, null);
		} catch (Exception e) {
			// log.error(e.getMessage());
		}

		return sslContext;
	}

	private File initKeyStoreFile(URL hostUrl) throws IOException {
		File keystoreFile = new File(DEFAULT_KS_DIR + File.separator + hostUrl.getHost() + "." + hostUrl.getPort()
				+ KEYSTORE_SUFFIX);
		if(!keystoreFile.exists()){
			char[] cPassword = KEYSTORE_PASSWORD.toCharArray();
			try {
				KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
				keyStore.load(null, cPassword);
				Certificate firstCert = getCertificate(hostUrl);
				keyStore.setCertificateEntry(hostUrl.getHost(), firstCert);
				keyStore.store(new BufferedOutputStream(new FileOutputStream(keystoreFile)), cPassword);
			} catch (Exception e) {
				// log.error(e.getMessage());
			}
		}
		return keystoreFile;
	}

	private SSLSocketFactory getSSLSocketFactory(SSLContext sslContext) {
		MySSLSocketFactory socketFactory = null;
		try {
			socketFactory = new MySSLSocketFactory(sslContext);
		} catch (KeyManagementException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (UnrecoverableKeyException e) {
			e.printStackTrace();
		}
		socketFactory.setHostnameVerifier(new X509HostnameVerifier() {
			public boolean verify(String arg0, SSLSession arg1) {
				return false;
			}

			public void verify(String arg0, SSLSocket arg1) throws IOException {}

			public void verify(String arg0, X509Certificate arg1) throws SSLException {}

			public void verify(String arg0, String[] arg1, String[] arg2) throws SSLException {}
		});

		return socketFactory;
	}

	/**
	 * @param username
	 * @param password
	 */
	public void authenticate(String username, String password) {
		UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password); //$NON-NLS-1$ //$NON-NLS-2$
		AuthScope httpAuthScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
		httpClient.getCredentialsProvider().setCredentials(httpAuthScope, credentials);
	}

	private String getProviderAlgorithm() {
		try {
			KeyManagerFactory.getInstance(SUN_X509_ALGORITHM);
			return SUN_X509_ALGORITHM;
		} catch (NoSuchAlgorithmException e1) {}
		return null;
	}

	private Certificate getCertificate(URL hostUrl) throws Exception {
		// Create a trust manager that does not validate certificate chains
		X509TrustManager[] trustAllCerts = new X509TrustManager[] { new X509TrustManager() {
			public java.security.cert.X509Certificate[] getAcceptedIssuers() {
				return null;
			}

			public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}

			public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
		} };
		// Install the all-trusting trust manager
		SSLContext sc = SSLContext.getInstance(SSL_ALGORITHM);
		sc.init(null, trustAllCerts, new java.security.SecureRandom());
		// Now use this connection to get the server's certificates
		HttpsURLConnection connection = (HttpsURLConnection) hostUrl.openConnection();
		connection.setHostnameVerifier(new HostnameVerifier() {
			public boolean verify(String hostname, SSLSession session) {
				return true;
			}
		});
		connection.setSSLSocketFactory(sc.getSocketFactory());
		connection.connect();
		Certificate certs[] = connection.getServerCertificates();
		connection.disconnect();
		return certs[0];
	}

	/**
	 * @return http client
	 */
	public DefaultHttpClient getHttpClient() {
		return httpClient;
	}

	/**
	 * @return http host
	 */
	public HttpHost getTargetHost() {
		return targetHost;
	}
}

class MySSLSocketFactory extends SSLSocketFactory {
	SSLContext sslContext = SSLContext.getInstance("TLS");

	public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException,
			KeyStoreException, UnrecoverableKeyException {
		super(truststore);

		TrustManager tm = new X509TrustManager() {

			public X509Certificate[] getAcceptedIssuers() {
				return null;
			}

			@Override
			public void checkClientTrusted(X509Certificate[] arg0, String arg1)
					throws java.security.cert.CertificateException {}

			@Override
			public void checkServerTrusted(X509Certificate[] arg0, String arg1)
					throws java.security.cert.CertificateException {}
		};

		sslContext.init(null, new TrustManager[] { tm }, null);
	}

	public MySSLSocketFactory(SSLContext context) throws KeyManagementException, NoSuchAlgorithmException,
			KeyStoreException, UnrecoverableKeyException {
		super(null);
		sslContext = context;
	}

	@Override
	public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException,
			UnknownHostException {
		return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
	}

	@Override
	public Socket createSocket() throws IOException {
		return sslContext.getSocketFactory().createSocket();
	}
}
